package com.project.javagobang.game;


import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.project.javagobang.JavaGobangApplication;
import com.project.javagobang.model.User;
import com.project.javagobang.model.UserMapper;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;

import java.io.IOException;
import java.util.UUID;

@Data
//@Component
//这个类就表示一个游戏房间
public class Room {
    //使用字符串类型来表示,方便生成唯一值.
    private String roomId;
    private User user1;
    private User user2;

    //先手玩家id
    private int whiteUser;
    private static final int MAX_ROW = 15;
    private static final int MAX_COL = 15;
    //创建objectmapper对象用来转化json对象
    private ObjectMapper objectMapper = new ObjectMapper();
    //@Autowired
    private OnlineUserManager onlineUserManager;
    //@Autowired//用于房间销毁
    private RoomManager roomManager;
    // @Resource
    private UserMapper userMapper;

    //这个二维数组用来表示棋盘
    //1).约定:使用0表示当前位置为落子,初始化好的int数组,就相当于全是0
    //2).使用1表示user1的落子位置
    //3).使用2表示user2的落子位置

    private int[][] board = new int[MAX_ROW][MAX_COL];

    public Room() {
        //构造Room的时候生成唯一的字符串表示房间id
        //使用UUID来作为房间id
        roomId = UUID.randomUUID().toString();
        //通过入口类中记录的context来手动获取到前面的roommanager 和onlineusermanager
        onlineUserManager = JavaGobangApplication.context.getBean(OnlineUserManager.class);
        roomManager = JavaGobangApplication.context.getBean(RoomManager.class);
        userMapper = JavaGobangApplication.context.getBean(UserMapper.class);

    }


    //通过这个方法处理一次落子操作
    public void putChess(String reqJson) throws IOException {
        //1.记录当前落子位置
        GameRequest request = objectMapper.readValue(reqJson, GameRequest.class);
        GameResponse response = new GameResponse();
        //当前这个子是玩家1落的,还是玩家2落得,根据这个玩家一还是玩家二来决定数组中是填1还是2

        int chess = (request.getUserId() == user1.getUserId()) ? 1 : 2;
        int row = request.getRow();
        int col = request.getCol();
        if (board[row][col] != 0) {
            //在客户端针对重复落子已经进行过判定,此处为了代码更加健壮,在服务器在判定一次
            System.out.println("当前位置 (" + row + "," + col + ") 已经有子了");
            return;
        }
        board[row][col] = chess;
        //2打印出当前的棋盘信息,方便来观察局势,也方便后面验证胜负关系的判定
        printBoard();
        //3.进行胜负判定
        int winner = checkWinner(row, col, chess);
        //4.给房间中的所有客户端都返回响应
        response.setMessage("putChess");
        response.setUserId(request.getUserId());
        response.setRow(row);
        response.setCol(col);
        response.setWinner(winner);

        //要想给用户放送websocket数据,就需要获取到这个用户的WebSocketSession
        WebSocketSession session1 = onlineUserManager.getFromGameRoom(user1.getUserId());
        WebSocketSession session2 = onlineUserManager.getFromGameRoom(user2.getUserId());
        //万一当前查到的会话为空(玩家已经下线了) 特殊处理一下
        if (session1 == null) {
            // 玩家1 掉线, 直接认为玩家2 获胜
            response.setWinner(user2.getUserId());
            System.out.println("玩家1 掉线!");
        }
        if (session2 == null) {
            // 玩家2 掉线, 直接认为玩家1 获胜
            response.setWinner(user1.getUserId());
            System.out.println("玩家2 掉线!");
        }
        //把响应构成的json字符串,通过session进行传输
        String respJson = objectMapper.writeValueAsString(response);
        if (session1 != null) {
            session1.sendMessage(new TextMessage(respJson));
        }
        if (session2 != null) {
            session2.sendMessage(new TextMessage(respJson));
        }

        // 5. 如果玩家胜负已分, 就把 room 从管理器中销毁
        if (response.getWinner() != 0) {
            //胜负已分
            System.out.println("游戏结束, 房间即将销毁! roomId: " + roomId + " 获胜⽅为: " + response.getWinner());
            //更新获胜方和失败方的信息
            int winUserId = response.getWinner();
            int loseUserId = (response.getWinner() == user1.getUserId()) ? user2.getUserId() : user1.getUserId();
            userMapper.userWin(winUserId);
            userMapper.userLose(loseUserId);

            roomManager.remove(roomId, user1.getUserId(), user2.getUserId());
        }
    }

    private void printBoard() {
        System.out.println("打印棋盘信息: " + roomId);
        System.out.println("===========================");
        for (int r = 0; r < MAX_ROW; r++) {
            for (int c = 0; c < MAX_COL; c++) {
                //针对一行的若干列,不要打印换行
                System.out.print(board[r][c] + " ");
            }
            System.out.println();
        }
        System.out.println("===========================");
    }


    //使用这个方法来判定当前落子是否分出胜负
    //约定如果玩家1获胜，就返回玩家1的userId
    //如果玩家2获胜，就返回玩家2的userId
    //如果胜负未分，就返回0
    private int checkWinner(int row, int col, int chess) {
        //TODO 一会在实现,使用这个方法.
        //以row, col为中⼼
        // 1. 检查所有的⾏(循环五次)
        for (int c = col - 4; c <= col; c++) {
            //针对其中一种情况,来判定五子是不是连在一起了
            //不光这五个子得连着,颜色还得一致
            try {
                if (board[row][c] == chess
                        && board[row][c + 1] == chess
                        && board[row][c + 2] == chess
                        && board[row][c + 3] == chess
                        && board[row][c + 4] == chess) {
                    return chess == 1 ? user1.getUserId() : user2.getUserId();
                }
            } catch (ArrayIndexOutOfBoundsException e) {
                //如果出现数组下标越界得情况,就在这里直接忽略这个异常
                continue;
            }

        }

        //2.检查所有列
        for (int r = row - 4; r <= row; r++) {
            //针对其中一种情况,来判定五子是不是连在一起了
            //不光这五个子得连着,颜色还得一致
            try {
                if (board[r][col] == chess
                        && board[r + 1][col] == chess
                        && board[r + 2][col] == chess
                        && board[r + 3][col] == chess
                        && board[r + 4][col] == chess) {
                    return chess == 1 ? user1.getUserId() : user2.getUserId();
                }
            } catch (ArrayIndexOutOfBoundsException e) {
                //如果出现数组下标越界得情况,就在这里直接忽略这个异常
                continue;
            }

        }

        //3.左对角线
        for (int r = row - 4, c = col - 4; r <= row && c <= col; r++, c++) {
            //针对其中一种情况,来判定五子是不是连在一起了
            //不光这五个子得连着,颜色还得一致
            try {
                if (board[r][c] == chess
                        && board[r + 1][c + 1] == chess
                        && board[r + 2][c + 2] == chess
                        && board[r + 3][c + 3] == chess
                        && board[r + 4][c + 4] == chess) {
                    return chess == 1 ? user1.getUserId() : user2.getUserId();
                }
            } catch (ArrayIndexOutOfBoundsException e) {
                //如果出现数组下标越界得情况,就在这里直接忽略这个异常
                continue;
            }

        }
        //4.右对角线
        for (int r = row - 4, c = col + 4; r <= row && c >= col; r++, c--) {
            //针对其中一种情况,来判定五子是不是连在一起了
            //不光这五个子得连着,颜色还得一致
            try {
                if (board[r][c] == chess
                        && board[r + 1][c - 1] == chess
                        && board[r + 2][c - 2] == chess
                        && board[r + 3][c - 3] == chess
                        && board[r + 4][c - 4] == chess) {
                    return chess == 1 ? user1.getUserId() : user2.getUserId();
                }
            } catch (ArrayIndexOutOfBoundsException e) {
                //如果出现数组下标越界得情况,就在这里直接忽略这个异常
                continue;
            }

        }

        //胜负未分,返回0
        return 0;
    }
}
